package com.wd.common.eventbus;

import android.os.Looper;

import com.wd.base.util.HandlerUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * NA<BR>
 *
 * @author W4255
 * @version [V0.0.1, 2021/7/5]
 * @Description: NA
 */
public class LbEventBus {
    private volatile static LbEventBus instance;
    private Map<Object, List<LbSubscribeMethod>> cacheMap;

    public LbEventBus() {

    }

    public static LbEventBus getDefault() {
        if (instance == null) {
            synchronized (LbEventBus.class) {
                if (instance == null) {
                    new LbEventBus();
                }
            }
        }
        return instance;
    }

    /**
     * 注册
     *
     * @param subscriber 注册者 通常是fragment activity
     */
    public void register(Object subscriber) throws Exception {
        //未注册则创建添加到缓存，反之不添加防止多次创建造成消耗
        List<LbSubscribeMethod> tmacSubscribeMethods = cacheMap.get(subscriber);
        if (tmacSubscribeMethods == null) {
            tmacSubscribeMethods = getSubScribeMethods(subscriber);
            cacheMap.put(subscriber, tmacSubscribeMethods);
        }

    }

    /**
     * 分析
     * 根据传入的订阅者，这里可以想象成是一个自定义的MyActivity。
     * 1.首先我们排除掉系统Activity类。
     * 2.获取到MyActivity所有的方法，通过declaredMethod.getAnnotation(TmacSubscribe.class);获取该方法是否带有自定义注解，如果存在则说明该方法是订阅方法。
     * 3.通过Class<?>[] parameterTypes = declaredMethod.getParameterTypes();获取参数列表，如果该参数个数不是1个则抛出运行时异常.
     * 4.构造TmacSubscribeMethod类，添加到缓存中。
     * 以上就是简单的注册方式。有了注册那必须要有一个反注册方法。
     * @param subscriber
     * @return
     */
    private List<LbSubscribeMethod> getSubScribeMethods(Object subscriber) throws Exception {
        //获取到订阅者类
        Class<?> aClass = subscriber.getClass();
        List<LbSubscribeMethod> list = new ArrayList<>();
        while (aClass != null) {
            //如果是系统的类直接忽略
            String name = aClass.getName();
            if (name.startsWith("java.") ||
                    name.startsWith("javax.") ||
                    name.startsWith("android.") ||
                    name.startsWith("androidx.")) {
                break;
            }

            //获取到所有方法
            Method[] declaredMethods = aClass.getDeclaredMethods();
            if (declaredMethods != null) {
                for (Method declaredMethod : declaredMethods) {
                    int modifiers = declaredMethod.getModifiers();
                    if ((modifiers & Modifier.PUBLIC) != 0) {
                        //获取每个方法上面的特定注解
                        LbSubscribe annotation = declaredMethod.getAnnotation(LbSubscribe.class);
                        if (annotation == null) {
                            continue;
                        }
                        //参数列表
                        Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
                        if (parameterTypes.length != 1) {
                            throw new Exception("@Subscribe method " + declaredMethod +
                                    "must have exactly 1 parameter but has " + parameterTypes.length);
                        }

                        if (annotation != null) {
                            //获取threadMode
                            LbSubscribeThreadMode tmacSubscribeThreadMode = annotation.THREAD_MODE();
                            list.add(new LbSubscribeMethod(declaredMethod, parameterTypes[0], tmacSubscribeThreadMode));

                        }
                    } else if (declaredMethod.isAnnotationPresent(LbSubscribe.class)) {
                        String methodName = declaredMethod.getDeclaringClass().getName() + "." + declaredMethod.getName();
                        try {
                            throw new Exception(methodName +
                                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }


                }
            }

            //一层一层判断
            aClass = aClass.getSuperclass();
        }

        return list;

    }
    /**
     * 反注册
     *
     * @param subscriber
     */
    public void unRegister(Object subscriber) {
        List<LbSubscribeMethod> list = cacheMap.get(subscriber);
        if (list != null) {
            cacheMap.remove(subscriber);
        }
    }

    /**
     * 分析
     * 其实比较简单，总的一句话就是：通过缓存我们就可以拿到所有的订阅者以及订阅者方法信息：
     * LbSubscribeMethod。根据制定的线程模式配合handler以及线程池做到线程切换指定订阅方法的执行线程。核心仍是 method.invoke("执行类","参数列表")
     *
     * 发送
     *
     * @param event 事件类型
     */
    public void post(Object event) {
        Set<Object> subscribeSet = cacheMap.keySet();
        Iterator<Object> subscribeIterator = subscribeSet.iterator();

        while (subscribeIterator.hasNext()) {
            //获取订阅者
            Object subscriber = subscribeIterator.next();
            List<LbSubscribeMethod> list = cacheMap.get(subscriber);
            for (LbSubscribeMethod LbSubscribeMethod : list) {
                if (LbSubscribeMethod.getEventType().isAssignableFrom(event.getClass())) {
                    switch (LbSubscribeMethod.getTmacThreadMode()) {
                        case MAIN:
                            //订阅者和发布者都在主线程
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                invoke(LbSubscribeMethod.getMethod(), subscriber, event);
                            } else {
                                //发布者异步线程发布，接收者在主线程，利用handler切换到主线程
                                HandlerUtils.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(LbSubscribeMethod.getMethod(), subscriber, event);
                                    }
                                });

                            }
                            return;
                        case ASYNC:
                            // 发布者在主线程发布， 订阅者在异步线程
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                HandlerUtils.execute(new Runnable() {
                                    @Override
                                    public void run() {
                                        invoke(LbSubscribeMethod.getMethod(), subscriber, event);
                                    }
                                });
                            } else {
                                //发布者和订阅者都在异步线程
                                invoke(LbSubscribeMethod.getMethod(), subscriber, event);

                            }

                            break;
                    }
                }
            }


        }
    }
    private void invoke(Method method, Object subscriber, Object params) {
        try {
            method.invoke(subscriber, params);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
